package com.beesphere.excel.reader.impls.xml;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import com.beesphere.excel.lang.LangUtils;
import com.beesphere.excel.reader.ExcelReaderException;
import com.beesphere.excel.reader.impls.AbstractExcelReader;
import com.qlogic.commons.utils.strings.StringUtils;

public class ExcelToXmlConverter extends AbstractExcelReader {

	private static final long serialVersionUID = -6342872176190811133L;
	
	protected OutputStream output;
	protected Writer writer;
	
	protected int linesCount;

	protected boolean omitXmlDeclaration;
	protected boolean ignoreEmptyCells = true;
	protected int sheetIndex = -1;
	protected String namespace;
	protected String [] xmlns;
	protected Charset charset = LangUtils.DEFAULT_CHARSET;
	protected int flushRate = 20;
	protected Map<String, String> tags = new HashMap<String, String> ();
	
	public ExcelToXmlConverter (OutputStream output) {
		this.output = output;
	}

	public ExcelToXmlConverter () {
		this (null);
	}
	
	@Override
	protected void visitStart (int count) throws ExcelReaderException {
		if (output == null) {
			output = System.out;
		}
		writer = new OutputStreamWriter (output, charset);
		try {
			if (!omitXmlDeclaration) {
				writer.write (LangUtils.XML_DECLARATION_START);
				writer.write (charset.name ());
				writer.write (LangUtils.XML_DECLARATION_START_END);
			}
			if (sheetIndex == -1) {
				writer.append (LangUtils.LESS);
				writeTag (LangUtils.ROOT, -1, -1);
				
				writeXmlns ();
				
				writer.append (LangUtils.GREATER);
			}
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
	}

	@Override
	protected void visitEnd () throws ExcelReaderException {
		try {
			if (sheetIndex == -1) {
				writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (LangUtils.ROOT, -1, -1);writer.write (LangUtils.GREATER);
			}
			writer.flush ();
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
	}

	@Override
	protected void visitStartSheet (int index, String title, int count)
			throws ExcelReaderException {
		if (sheetIndex != -1 && sheetIndex != index) {
			sheetConfig.setSkip (true);
			return;
		}
		linesCount = count;
		try {
			writer.write (LangUtils.LESS);
			writeTag (LangUtils.SHEET, -1, -1);
			
			if (sheetIndex != -1) {
				writeXmlns ();
			}
			
			writer.write (LangUtils.GREATER);
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
		sheetConfig.setWithHeader(isSheetWithHeader (index));
		sheetConfig.setColumnsIndexes(getSheetColumnsIndexes(index));
		sheetConfig.setSkip (false);
	}

	@Override
	protected boolean visitEndSheet (int index) throws ExcelReaderException {
		if (sheetIndex != -1 && sheetIndex != index) {
			return true;
		}
		try {
			if (linesCount > 0) {
				writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (LangUtils.RS, -1, -1);writer.write (LangUtils.GREATER);
			}
			writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (LangUtils.SHEET, -1, -1);writer.write (LangUtils.GREATER);
			writer.flush ();
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
		return sheetIndex != index;
	}

	@Override
	protected boolean visitLine (Object[] line, int lineNum, boolean withHeader)
			throws ExcelReaderException {
		try {
			if (lineNum == 0) {
				if (withHeader) {
					writer.write (LangUtils.LESS);writeTag (LangUtils.HS, lineNum, -1);writer.write (LangUtils.GREATER);
					writeLine (line, lineNum, null, LangUtils.H);
					writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (LangUtils.HS, lineNum, -1);writer.write (LangUtils.GREATER);
					writer.write (LangUtils.LESS);writeTag (LangUtils.RS, lineNum, -1);writer.write (LangUtils.GREATER);
				} else {
					writer.write (LangUtils.LESS);writeTag (LangUtils.RS, lineNum, -1);writer.write (LangUtils.GREATER);
					writeLine (line, lineNum, LangUtils.R, LangUtils.C);
				}
			} else {
				writeLine (line, lineNum, LangUtils.R, LangUtils.C);
			}
			if (lineNum%flushRate == 0) {
				writer.flush ();
			}
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
		return true;
	}
	
	public boolean isSheetWithHeader (int sheetIndex) {
		return false;
	}

	public int[] getSheetColumnsIndexes (int sheetIndex) {
		return null;
	}

	@Override
	public void reset () {
		super.reset ();
		output = null;
		writer = null;
		omitXmlDeclaration = false;
		ignoreEmptyCells = true;
		namespace = null;
		xmlns = null;
		linesCount = 0;
		sheetIndex = -1;
	}
	
	private void writeXmlns () throws IOException {
		if (xmlns != null && xmlns.length > 0) {
			for (String xn : xmlns) {
				writer.write (LangUtils.SPACE);
				writer.write (xn);
			}
		}	
	}

	protected void writeTag (String name, int recordNumber, int column) throws IOException {
		if (name == null) {
			return;
		}
		if (tags == null) {
			writer.write (name);
			return;
		}
		if (namespace != null) {
			writer.append (namespace).append (LangUtils.COLON);
		}
		if (tags.get (name) != null) {
			writer.write (tags.get (name));
		} else {
			writer.write (name);
		}
		if (name.equals (LangUtils.H) || name.equals (LangUtils.C)) {
			writer.write (LangUtils.UNDERSCORE);writer.write (String.valueOf (column));
		}
		writer.flush ();
	}
	
	protected void writeLine (Object [] line, int lineNum, String tag, String cellTag) throws IOException {
		if (tag != null) {
			writer.write (LangUtils.LESS);writeTag (tag, lineNum, -1);writer.write (LangUtils.GREATER);
		}
		for (int i = 0; i < line.length; i++) {
			Object cell = line [i];
			if (ignoreEmptyCells && (cell == null || String.valueOf(cell).trim ().equals (LangUtils.EMPTY))) {
				continue;
			}
			String cellValue;
			if (!ignoreEmptyCells && cell == null) {
				cellValue = LangUtils.EMPTY;
			} else {
				cellValue = StringUtils.escapeXML (String.valueOf(cell));
			}
			writer.write (LangUtils.LESS);writeTag (cellTag, lineNum, i);writer.write (LangUtils.GREATER);
			writer.write (cellValue);
			writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (cellTag, lineNum, i);writer.write (LangUtils.GREATER);
		}
		if (tag != null) {
			writer.write (LangUtils.LESS);writer.write (LangUtils.SLASH);writeTag (tag, lineNum, -1);writer.write (LangUtils.GREATER);
		}
	}

	public Charset getCharset() {
		return charset;
	}

	public void setCharset(Charset charset) {
		this.charset = charset;
	}

	public boolean isOmitXmlDeclaration() {
		return omitXmlDeclaration;
	}

	public void setOmitXmlDeclaration(boolean omitXmlDeclaration) {
		this.omitXmlDeclaration = omitXmlDeclaration;
	}

	public String getNamespace() {
		return namespace;
	}

	public void setNamespace(String namespace) {
		this.namespace = namespace;
	}

	public String[] getXmlns() {
		return xmlns;
	}

	public void setXmlns(String[] xmlns) {
		this.xmlns = xmlns;
	}

	public Map<String, String> getTags() {
		return tags;
	}

	public void setTags(Map<String, String> tags) {
		this.tags = tags;
	}

	public boolean isIgnoreEmptyCells() {
		return ignoreEmptyCells;
	}

	public void setIgnoreEmptyCells(boolean ignoreEmptyCells) {
		this.ignoreEmptyCells = ignoreEmptyCells;
	}

	public int getSheetIndex() {
		return sheetIndex;
	}

	public void setSheetIndex(int sheetIndex) {
		this.sheetIndex = sheetIndex;
	}

	public int getFlushRate() {
		return flushRate;
	}

	public void setFlushRate(int flushRate) {
		this.flushRate = flushRate;
	}

}
